unit UUndo;

interface

uses
  Math;

type

  RUndo = record
    xcord: word;
    ycord: word;
    Layer: byte; // 0 - idx, 1- dcr, 2-obj, 3-item
    idx: integer; // for undo
    redx: Integer; // for redo
    group: Integer;
  end;

  T2IntEvent = procedure(a, b: Integer) of object;
  T4IntEvent = procedure(a, b, c, d: Integer) of object;

  TUndo = class(TObject) // tcomponent ancestor for autodestruction
  private
    OnChange: T4IntEvent;
    OnDraw: T2IntEvent;
    AUndol: Cardinal;
    FCurUndo: Cardinal;
    Aundo: array of RUndo;
    FCurGroup: Cardinal;
    InGroup: Boolean;
    MaxGroup: Cardinal;
    MaxUndo: Cardinal;
    procedure setundo(const Value: cardinal);
    procedure SetGroup(const Value: Cardinal);
    property Curgroup: Cardinal read FCurGroup write SetGroup;
  public
    constructor Create(changeevt: T4IntEvent; drawevt: T2IntEvent);
    procedure Clear;
    property curundo: cardinal read fcurundo write setundo;
    procedure Add(xcord, ycord, Layer, Idx, redx: Word);
    procedure StartGroup();
    procedure EndGroup();
    procedure Undo;
    procedure Redo;

  end;

function BuildUndo(xcord, ycord, Layer, idx, redx, group: Integer): RUndo;

implementation

function BuildUndo(xcord, ycord, Layer, idx, redx, group: Integer): RUndo;
begin
  Result.xcord := xcord;
  Result.ycord := ycord;
  Result.Layer := Layer;
  Result.idx := idx;
  Result.redx := redx;
  Result.group := group;
end;

{ Tundo }

procedure TUndo.Add(xcord, ycord, Layer, Idx, redx: Word);
begin
  if MaxUndo = curundo then
    MaxUndo := curundo + 1;
  curundo := curundo + 1;
  if (Curgroup < MaxGroup) then
    MaxGroup := Curgroup; // for clearing all redos on adding after undo
  if not InGroup then
    CurGroup := CurGroup + 1;
  AUndo[curundo] := BuildUndo(xcord, ycord, Layer, Idx, redx, CurGroup);
end;

procedure TUndo.Clear;
begin
  FCurGroup := 0;
  curundo := 0;
  aundol := 0;
  setlength(aundo, aundol);
end;

constructor TUndo.Create(changeevt: T4IntEvent; drawevt: T2IntEvent);
begin
  inherited Create();
  OnDraw := drawevt;
  OnChange := changeevt;
end;

procedure TUndo.EndGroup;
begin
  InGroup := False;
end;


procedure Tundo.Redo;
var
  Cur: Cardinal;
begin
  if not Assigned(OnChange) or not Assigned(OnDraw) or (Curgroup = MaxGroup) then
    exit;
  CurGroup := CurGroup + 1;
  repeat
    
    with aundo[curundo] do
    begin // restore changes via redo
      OnChange(xcord, ycord, Layer, redx);
      OnDraw(xcord, ycord); // draw tile on a grid
    end;
    curundo := curundo + 1;
  until (aundo[curundo].group <> CurGroup) or (Curundo = Maxundo);
end;

procedure TUndo.SetGroup(const Value: Cardinal);
begin
  if value = -1 then
    Exit;
  if (Curgroup = MaxGroup) and (Value - Curgroup = 1) then
    MaxGroup := Value;
  FCurGroup := Value;
end;

procedure Tundo.setundo(const Value: cardinal);
begin
  if Value > maxundo then
    Exit;
  fcurundo := value;
  if aundol <= Value then
  begin // increase undolimit
    inc(aundol, 100);
    setlength(aundo, aundol);
  end;
end;

procedure TUndo.StartGroup;
begin
  Curgroup := Curgroup + 1;
  InGroup := True;
end;

procedure Tundo.Undo;
begin
  if (curundo = 0) or not Assigned(OnChange) or not Assigned(OnDraw) then
    exit;
  while aundo[curundo].group = CurGroup do
    with aundo[curundo] do
    begin // restore changes via undo
      curundo := curundo - 1;
      OnChange(xcord, ycord, Layer, idx);
      OnDraw(xcord, ycord); // draw tile on a grid
    end;
  CurGroup := CurGroup - 1;
end;

end.

